
bl_info = {
    "name": "Import OSSL LLM File",
    "author": "Tom Ernst",
    "version": (1, 0, 0),
    "blender": (2, 93, 0),
    "location": "Topbar > OSSL Menu > Import OSSL LLM File",
    "description": "Import OSSL Old Mesh File",
    "warning": "",
    "doc_url": "https://rowlplay.net/blender/llmimp.txt",
    "category": "Import-Export",
}

import bpy, os
from math import pi
from struct import unpack
from mathutils import Vector, Matrix, Euler
from ossl import util
import bmesh

def pp(s):
    return
    f = open("C:\\logs\\bl1.txt", "a")
    f.write("\n" + s)
    f.close()

Name_to_PNG = {
    "head": "SL-Avatar-Head-1024",
    "upper_body": "SL-Avatar-Upper-1024",
    "lower_body": "SL-Avatar-Lower-1024",
    "eye": "",
    "eyelashes": "",
    "hair": "",
    "skirt": "SL-Avatar-Skirt-1024"
}

class Format_LLM(object):
    def __init__(self, name):
        self.name = name
        self.title = ""
        self.hasWeights = False
        self.hasDetailTexCoords = False
        self.position = Vector((0,0,0))
        self.rotation = Euler((0,0,0), "XYZ")
        self.rotationOrder = 0
        self.scale = Vector((0,0,0))
        self.numVerts = 0
        self.verts = []
        self.norms = []
        self.binorms = []
        self.texCoords = []
        self.weights = []
        self.detailTexCoords = []
        self.numFaces = 0
        self.faces = []
        self.numSkinJoints = 0
        self.skinJoints = []
        self.numMorphs = 0
        self.morphs = []
        self.numRemaps = 0
        self.remaps = []
        
        self.male_morph_indices = []
        
        self.file_import(util.path("resources\\avatar_" + self.name + ".llm"))
    
    def read_string(self, len):
        ba = self.infile.read(len)
        for i in range(0, len):
            if (ba[i] == 0): break
        return bytes(ba[0:i]).decode('UTF-8')

    def file_import(self, filepath):
        print("importing from", filepath)
        self.infile = open(filepath, 'rb')
        try:
            self.title = self.read_string(24) # bytes(ba[0:i]).decode('UTF-8')
            print("\ttitle: %s" % self.title)
            
            self.hasWeights = (self.infile.read(1)[0] != 0)
            print("\thasWeights: %i" % self.hasWeights)

            self.hasDetailTexCoords = (self.infile.read(1)[0] != 0)
            print("\thasDetailTexCoords: %i" % self.hasDetailTexCoords)
            
            self.position = Vector(unpack("fff", self.infile.read(12)))
            print("\tposition:", self.position)

            self.rotation = Euler(unpack("fff", self.infile.read(12)), "XYZ")
            self.rotationOrder = self.infile.read(1)[0]
            if (self.rotationOrder != 0): self.rotation.order = "???"
            print("\trotation:", self.rotation)

            self.scale = Vector(unpack("fff", self.infile.read(12)))
            print("\tscale:", self.scale)

            self.numVerts = unpack("H", self.infile.read(2))[0]
            print("\tnumVertices: %i" % self.numVerts)
            
            for i in range(0, self.numVerts):
                self.verts.append(Vector(unpack("fff", self.infile.read(12))))
            print("\tlen(vertices): %i" % len(self.verts))

            for i in range(0, self.numVerts):
                self.norms.append(Vector(unpack("fff", self.infile.read(12))))
            print("\tlen(normals): %i" % len(self.norms))

            for i in range(0, self.numVerts):
                self.binorms.append(Vector(unpack("fff", self.infile.read(12))))
            print("\tlen(binormals): %i" % len(self.binorms))

            for i in range(0, self.numVerts):
                self.texCoords.append(Vector(unpack("ff", self.infile.read(8))))
            print("\tlen(texCoords): %i" % len(self.texCoords))

            if self.hasDetailTexCoords:
                for i in range(0, self.numVerts):
                    self.detailTexCoords.append(unpack("ff", self.infile.read(8)))
                print("\tlen(detailTexCoords): %i" % len(self.detailTexCoords))

            if self.hasWeights:
                mmw = [0]*100
                for i in range(0, self.numVerts):
                    self.weights.append(unpack("f", self.infile.read(4))[0])
                    mw = (int)(self.weights[-1])
                    mmw[mw] += 1
                print("\tlen(weights): %i" % len(self.weights))
                for i in range(0, len(mmw)):
                    if (mmw[i] != 0): print("\t\t#weights for %i: %i" % (i, mmw[i]))

            self.numFaces = unpack("H", self.infile.read(2))[0]
            print("\tnumFaces: %i" % self.numFaces)
            
            for i in range(0, self.numFaces):
                self.faces.append(unpack("HHH", self.infile.read(6)))
            print("\tlen(faces): %i" % len(self.faces))

            if self.hasWeights:
                self.numSkinJoints = unpack("H", self.infile.read(2))[0]
                print("\tnumSkinJoints: %i" % self.numSkinJoints)
            
                for i in range(0, self.numSkinJoints):
                    self.skinJoints.append(self.read_string(64))
                    print("\t\tskinJoint(%i): %s" % (i, self.skinJoints[-1]))

            print("\tMorphs")
            while True:
                s = self.read_string(64)
                if s == "End Morphs": break

                nV = unpack("i", self.infile.read(4))[0]
                print("\t\t%s – numVerts: %i" % (s, nV))

                v = []
                for i in range(0, nV):
                    vix = unpack("i", self.infile.read(4))[0]
                    co = Vector(unpack("fff", self.infile.read(12)))
                    nm = Vector(unpack("fff", self.infile.read(12)))
                    bi = Vector(unpack("fff", self.infile.read(12)))
                    tx = Vector(unpack("ff", self.infile.read(8)))
                    v.append((vix, co, nm, bi, tx))
                    # vertex-index, location, normal, binormal, texco
            
                if s.lower().find("male") >= 0: self.male_morph_indices.append(len(self.morphs))
                self.morphs.append((s, v))
            
            dummy = self.infile.read(4)
            if (len(dummy) != 4): print("EOF reached, no remaps")
            else:
                self.numRemaps = unpack("i", dummy)[0]
            
                for i in range(0, self.numRemaps):
                    self.remaps.append(unpack("ii", self.infile.read(8)))
                
                print("\tlen(remaps): %i" % len(self.remaps));
                dummy = self.infile.read(1)

            if (len(dummy) == 0): print("EOF reached")
            else: print("EOF not reached")

        finally:
            self.infile.close()

        print("LLM Imported")
        
    def gen_mesh(self):
        util.setMode("OBJECT")

        if bpy.data.objects.get(self.name) != None:
            bpy.data.objects.remove(bpy.data.objects.get(self.name))
            
        bpy.data.orphans_purge()

        bpy.ops.mesh.primitive_vert_add()
        bpy.ops.mesh.delete()

        util.setMode("OBJECT")

        obj = bpy.context.active_object
        obj.data.name = self.name
        obj.name = self.name
        
        util.setMode("EDIT")

        bm = bmesh.from_edit_mesh(obj.data)
        bm.verts.ensure_lookup_table()
        bm.edges.ensure_lookup_table()
        bm.faces.ensure_lookup_table()

        for v in self.verts: bm.verts.new(v)
        bm.verts.index_update()
        bm.verts.ensure_lookup_table()

        for f in self.faces: bm.faces.new([bm.verts[i] for i in f])
        bm.faces.index_update()
        bm.faces.ensure_lookup_table()
        
        for i in range(0, self.numVerts): bm.verts[i].normal = self.norms[i]
        
        bpy.ops.mesh.select_all(action="SELECT")
        bpy.ops.mesh.faces_shade_smooth()

        bm.loops.layers.uv.verify()

        bmesh.update_edit_mesh(obj.data)
        bm.free()

        util.setMode("OBJECT")
        
        for face in obj.data.polygons:
            for vert_idx, loop_idx in zip(face.vertices, face.loop_indices):
                obj.data.uv_layers.active.data[loop_idx].uv = self.texCoords[vert_idx]
        
        mat = bpy.data.materials.get(self.name)

        if mat is None:
            mat = bpy.data.materials.new(name=self.name)
            mat.use_nodes = True

            img = bpy.data.images.get(self.name)
            if img is None:
                imgFileName = Name_to_PNG[self.name]
                if imgFileName != "":
                    filepath = util.path("resources\\" + imgFileName + ".png")
                    img = bpy.data.images.load(filepath)
                    img.name = self.name

            bsdf = mat.node_tree.nodes["Principled BSDF"]

            imgshader = mat.node_tree.nodes.get(self.name + "_shader")
            if imgshader is None:
                imgshader = mat.node_tree.nodes.new('ShaderNodeTexImage')
                imgshader.name = self.name + "_shader"

            if img is not None: imgshader.image = img
            mat.node_tree.links.new(bsdf.inputs['Base Color'], imgshader.outputs['Color'])

        if obj.data.materials: obj.data.material[0] = mat
        else: obj.data.materials.append(mat)
        
        self.morph(obj, [])#"Male_Head", "Male_Torso", "Male_Legs", "Male_Package" ])

        return {'FINISHED'}

    def morph(self, obj, morph_selector):
        util.setMode("EDIT")

        bm = bmesh.from_edit_mesh(obj.data)
        bm.verts.ensure_lookup_table()
        bm.edges.ensure_lookup_table()
        bm.faces.ensure_lookup_table()
        
        uvs = {}
        ms = [morph for morph in self.morphs if morph[0] in morph_selector]

        for name, vert_morphs in ms:
            print("\tmorphing", name)
            for vix, offset, normal, binormal, texco in vert_morphs:
                bm.verts[vix].co += offset
                uvs[vix] = texco

        bmesh.update_edit_mesh(obj.data)
        bm.free()

        util.setMode("OBJECT")
        
        uv_layer = obj.data.uv_layers.active
        for face in obj.data.polygons:
            for vert_idx, loop_idx in zip(face.vertices, face.loop_indices):
                if uvs.get(vert_idx) is not None:
                    #if uvs[vert_idx] != Vector((0,0)): print("\t\t", vert_idx, uvs[vert_idx])
                    uv_layer.data[loop_idx].uv += uvs[vert_idx]
        
        return {'FINISHED'}

def register():
    print("registering: OSSL Format LLM")
    print("registered: OSSL Format LLM")

def unregister():
    print("unregistering: OSSL Format LLM")
    print("unregistered: OSSL Format LLM")

import ossl.util as util

if __name__ == "__main__":
    #util.reset_addon()

    Format_LLM("head").gen_mesh()
    #Format_LLM("eye").gen_mesh()
    #Format_LLM("eyelashes").gen_mesh()
    #Format_LLM("hair").gen_mesh()
    Format_LLM("upper_body").gen_mesh()
    #Format_LLM("skirt").gen_mesh()
    Format_LLM("lower_body").gen_mesh()
